/* $Id: inndchannel.c,v 1.5 2004/12/07 18:58:39 sby Exp $ */

#include "innbbsconf.h"
#include "daemon.h"
#include "bbslib.h"
#include "../include/bbs.h"

#define DEBUG
#undef DEBUG

#ifndef MAXCLIENT
#define MAXCLIENT 10
#endif

#ifndef ChannelSize
#define ChannelSize 4096
#endif

#ifndef ReadSize
#define ReadSize 1024
#endif

#ifndef DefaultINNBBSPort
#define DefaultINNBBSPort "7777"
#endif

#ifndef HIS_MAINT
#define HIS_MAINT
#define HIS_MAINT_HOUR 5
#define HIS_MAINT_MIN  30
#endif

int             Maxclient = MAXCLIENT;
ClientType     *Channel = NULL;
ClientType      INNBBSD_STAT;

int             Max_Art_Size = MAX_ART_SIZE;

int             inetdstart = 0;

int             Junkhistory = 0;

char           *REMOTEUSERNAME, *REMOTEHOSTNAME;

static fd_set   rfd, wfd, efd, orfd, owfd, oefd;

clearfdset(fd)
  int             fd;
{
  FD_CLR(fd, &rfd);
}

static
channelcreate(client)
  ClientType     *client;
{
  buffer_t       *in, *out;
  in = &client->in;
  out = &client->out;
  if (in->data != NULL)
    free(in->data);
  in->data = (char *)mymalloc(ChannelSize);
  in->left = ChannelSize;
  in->used = 0;
  if (out->data != NULL)
    free(out->data);
  out->data = (char *)mymalloc(ChannelSize);
  out->used = 0;
  out->left = ChannelSize;
  client->ihavecount = 0;
  client->ihaveduplicate = 0;
  client->ihavefail = 0;
  client->ihavesize = 0;
  client->statcount = 0;
  client->statfail = 0;
  client->begin = time(NULL);
}

channeldestroy(client)
  ClientType     *client;
{
  if (client->in.data != NULL) {
    free(client->in.data);
    client->in.data = NULL;
  }
  if (client->out.data != NULL) {
    free(client->out.data);
    client->out.data = NULL;
  }
#if !defined(PowerBBS) && !defined(DBZSERVER)
  if (client->ihavecount > 0 || client->statcount > 0) {
    bbslog("%s@%s rec: %d dup: %d fail: %d size: %d, stat rec: %d fail: %d, time sec: %d\n",
	   client->username, client->hostname, client->ihavecount,
	   client->ihaveduplicate, client->ihavefail, client->ihavesize,
	   client->statcount, client->statfail, time(NULL) - client->begin);
    INNBBSD_STAT.ihavecount += client->ihavecount;
    INNBBSD_STAT.ihaveduplicate += client->ihaveduplicate;
    INNBBSD_STAT.ihavefail += client->ihavefail;
    INNBBSD_STAT.ihavesize += client->ihavesize;
    INNBBSD_STAT.statcount += client->statcount;
    INNBBSD_STAT.statfail += client->statfail;
  }
#endif
}

inndchannel(port, path)
  char           *port, *path;
{
  time_t          tvec;
  int             i;
  int             bbsinnd;
  int             localbbsinnd;
  char            obuf[4096];
  struct timeval  tout;
  ClientType     *client = (ClientType *) mymalloc(sizeof(ClientType) * Maxclient);
  int             localdaemonready = 0;
  Channel = client;

  bbsinnd = pmain(port);
  if (bbsinnd < 0) {
    perror("pmain, existing");
    docompletehalt();
    return (-1);
  }
  FD_ZERO(&rfd);
  FD_ZERO(&wfd);
  FD_ZERO(&efd);

  localbbsinnd = p_unix_main(path);
  if (localbbsinnd < 0) {
    perror("local pmain, existing");
    /*
     * Kaede if (!inetdstart) fprintf(stderr, "if no other innbbsd running,
     * try to remove %s\n",path);
     */
    close(bbsinnd);
    return (-1);
  } else {
    FD_SET(localbbsinnd, &rfd);
    localdaemonready = 1;
  }

  FD_SET(bbsinnd, &rfd);
  tvec = time((time_t *) 0);
  for (i = 0; i < Maxclient; ++i) {
    client[i].fd = -1;
    client[i].access = 0;
    client[i].buffer[0] = '\0';
    client[i].mode = 0;
    client[i].in.left = 0;
    client[i].in.used = 0;
    client[i].in.data = NULL;
    client[i].out.left = 0;
    client[i].out.used = 0;
    client[i].out.data = NULL;
    client[i].midcheck = 1;
  }
  for (;;) {
    int             nsel, i;

    /*
     * When to maintain history files.
     */
    time_t          now;
    static int      maint = 0;
    struct tm      *local;

    if (INNBBSDshutdown()) {
      HISclose();
      bbslog(" Shutdown Complete \n");
      docompletehalt();
      exit(0);
    }
    time(&now);
    local = localtime(&now);
    if (local != NULL & local->tm_hour == His_Maint_Hour &&
	local->tm_min >= His_Maint_Min) {
      if (!maint) {
	bbslog(":Maint: start (%d:%d).\n", local->tm_hour, local->tm_min);
	HISmaint();
	time(&now);
	local = localtime(&now);
	if (local != NULL)
	  bbslog(":Maint: end (%d:%d).\n", local->tm_hour, local->tm_min);
	maint = 1;
      }
    } else {
      maint = 0;
    }
    /*
     * */
    /*
     * in order to maintain history, timeout every 60 seconds in case no
     * connections
     */
    tout.tv_sec = 60;
    tout.tv_usec = 0;
    orfd = rfd;
    if ((nsel = select(FD_SETSIZE, &orfd, NULL, NULL, &tout)) < 0) {
      continue;
    }
    if (localdaemonready && FD_ISSET(localbbsinnd, &orfd)) {
      int             ns, length;
      int             cc;
      ns = tryaccept(localbbsinnd);
      if (ns < 0)
	continue;
      for (i = 0; i < Maxclient; ++i) {
	if (client[i].fd == -1)
	  break;
      }
      if (i == Maxclient) {
	static char     msg[] = "502 no free descriptors\r\n";
	printf("%s", msg);
	write(ns, msg, sizeof(msg));
	close(ns);
	continue;
      }
      client[i].fd = ns;
      client[i].buffer[0] = '\0';
      client[i].mode = 0;
      client[i].midcheck = 1;
      channelcreate(&client[i]);
      FD_SET(ns, &rfd);		/* FD_SET(ns,&wfd); */
      {
	strncpy(client[i].username, "localuser", 20);
	strncpy(client[i].hostname, "localhost", 128);
	client[i].Argv.in = fdopen(ns, "r");
	client[i].Argv.out = fdopen(ns, "w");
#if !defined(PowerBBS) && !defined(DBZSERVER)
	bbslog("connected from (%s@%s).\n", client[i].username, client[i].hostname);
#endif
#ifdef INNBBSDEBUG
	printf("connected from (%s@%s).\n", client[i].username, client[i].hostname);
#endif
#ifdef DBZSERVER
	fprintf(client[i].Argv.out, "200 %s InterNetNews DBZSERVER server %s (%s@%s).\r\n", MYBBSID, VERSION, client[i].username, client[i].hostname);
#else
	fprintf(client[i].Argv.out, "200 %s InterNetNews INNBBSD server %s (%s@%s).\r\n", MYBBSID, VERSION, client[i].username, client[i].hostname);
#endif
	fflush(client[i].Argv.out);
	verboselog("UNIX Connect from %s@%s\n", client[i].username, client[i].hostname);
      }
    }
    if (FD_ISSET(bbsinnd, &orfd)) {
      int             ns = tryaccept(bbsinnd), length;
      struct sockaddr_in6 there;
      char           *name;
      int             cc;
      if (ns < 0)
	continue;
      for (i = 0; i < Maxclient; ++i) {
	if (client[i].fd == -1)
	  break;
      }
      if (i == Maxclient) {
	static char     msg[] = "502 no free descriptors\r\n";
	printf("%s", msg);
	write(ns, msg, sizeof(msg));
	close(ns);
	continue;
      }
      client[i].fd = ns;
      client[i].buffer[0] = '\0';
      client[i].mode = 0;
      client[i].midcheck = 1;
      channelcreate(&client[i]);
      FD_SET(ns, &rfd);		/* FD_SET(ns,&wfd); */
      length = sizeof(there);
      if (getpeername(ns, (struct sockaddr *) & there, &length) >= 0) {
	time_t          now = time((time_t *) 0);
	char            hostname[128];

	name = (char *)my_rfc931_name(ns, &there);
	strncpy(client[i].username, name, 20);

	if (ipv6_dns_name((struct sockaddr *) & there, hostname, 128))
	  return -1;

	strncpy(client[i].hostname, hostname, 128);

	client[i].Argv.in = fdopen(ns, "r");
	client[i].Argv.out = fdopen(ns, "w");
	if ((char *)search_nodelist(client[i].hostname, client[i].username) == NULL) {
	  bbslog(":Err: invalid connection (%s@%s).\n", client[i].username, client[i].hostname);
	  fprintf(client[i].Argv.out, "502 You are not in my access file. (%s@%s)\r\n", client[i].username, client[i].hostname);
	  fflush(client[i].Argv.out);
	  fclose(client[i].Argv.in);
	  fclose(client[i].Argv.out);
	  close(client[i].fd);
	  FD_CLR(client[i].fd, &rfd);
	  client[i].fd = -1;
	  continue;
	}
	bbslog("connected from (%s@%s).\n", client[i].username, client[i].hostname);
#ifdef INNBBSDEBUG
	printf("connected from (%s@%s).\n", client[i].username, client[i].hostname);
#endif
#ifdef DBZSERVER
	fprintf(client[i].Argv.out, "200 %s InterNetNews DBZSERVER server %s (%s@%s).\r\n", MYBBSID, VERSION, client[i].username, client[i].hostname);
#else
	fprintf(client[i].Argv.out, "200 %s InterNetNews INNBBSD server %s (%s@%s).\r\n", MYBBSID, VERSION, client[i].username, client[i].hostname);
#endif
	fflush(client[i].Argv.out);
	verboselog("INET Connect from %s@%s\n", client[i].username, client[i].hostname);
      } else {
      }

    }
    for (i = 0; i < Maxclient; ++i) {
      int             fd = client[i].fd;
      if (fd < 0) {
	continue;
      }
      if (FD_ISSET(fd, &orfd)) {
	int             nr;
#ifdef DEBUG
	printf("before read i %d in.used %d in.left %d\n", i, client[i].in.used, client[i].in.left);
#endif
	nr = channelreader(client + i);
#ifdef DEBUG
	printf("after read i %d in.used %d in.left %d\n", i, client[i].in.used, client[i].in.left);
#endif
	/* int nr=read(fd,client[i].buffer,1024); */
	if (nr <= 0) {
	  FD_CLR(fd, &rfd);
	  fclose(client[i].Argv.in);
	  fclose(client[i].Argv.out);
	  close(fd);
	  client[i].fd = -1;
	  channeldestroy(client + i);
	  continue;
	}
#ifdef DEBUG
	printf("nr %d %.*s", nr, nr, client[i].buffer);
#endif
	if (client[i].access == 0) {
	  continue;
	}
      }
    }
  }
}

int 
channelreader(client)
  ClientType     *client;
{
  int             len, clientlen;
  char            buffer1[8192], buffer2[4096];
  char           *ptr;
  buffer_t       *in = &client->in;

  if (in->left < ReadSize + 3) {
    int             need = in->used + in->left + ReadSize + 3;
    need += need / 5;
    in->data = (char *)myrealloc(in->data, need);
    in->left = need - in->used;
    verboselog("channelreader realloc %d\n", need);
  }
  len = read(client->fd, in->data + in->used, ReadSize);

  if (len <= 0)
    return len;

  in->data[len + in->used] = '\0';
  in->lastread = len;
#ifdef DEBUG
  printf("after read lastread %d\n", in->lastread);
  printf("len %d client %d\n", len, strlen(in->data + in->used));
#endif

  REMOTEHOSTNAME = client->hostname;
  REMOTEUSERNAME = client->username;
  if (client->mode == 0) {
    if ((ptr = (char *)strchr(in->data, '\n')) != NULL) {
      if (in->data[0] != '\r')
	commandparse(client);
    }
  } else {
    commandparse(client);
  }
  return len;
}

commandparse(client)
  ClientType     *client;
{
  char           *ptr, *lastend;
  argv_t         *Argv = &client->Argv;
  int             (*Main) ();
  char           *buffer = client->in.data;
  int             fd = client->fd;
  buffer_t       *in = &client->in;
  int             dataused;
  int             dataleft;

#ifdef DEBUG
  printf("%s %s buffer %s", client->username, client->hostname, buffer);
#endif
  ptr = (char *)strchr(in->data + in->used, '\n');
  if (client->mode == 0) {
    if (ptr == NULL) {
      in->used += in->lastread;
      in->left -= in->lastread;
      return;
    } else {
      dataused = ptr - (in->data + in->used) + 1;
      dataleft = in->lastread - dataused;
      lastend = ptr + 1;
    }
  } else {
    if (in->used >= 5) {
      ptr = (char *)strstr(in->data + in->used - 5, "\r\n.\r\n");
    } else if (strncmp(in->data, ".\r\n", 3) == 0) {
      ptr = in->data;
    } else {
      ptr = (char *)strstr(in->data + in->used, "\r\n.\r\n");
    }
    if (ptr == NULL) {
      in->used += in->lastread;
      in->left -= in->lastread;
      return;
    } else {
      ptr[2] = '\0';
      if (strncmp(in->data, ".\r\n", 3) == 0)
	dataused = 3;
      else
	dataused = ptr - (in->data + in->used) + 5;
      dataleft = in->lastread - dataused;
      lastend = ptr + 5;
      verboselog("Get: %s@%s end of data . size %d\n", client->username, client->hostname, in->used + dataused);
      client->ihavesize += in->used + dataused;
    }
  }
  if (client->mode == 0) {
    struct Daemoncmd *dp;
    Argv->argc = 0, Argv->argv = NULL,
      Argv->inputline = buffer;
    if (ptr != NULL)
      *ptr = '\0';
    verboselog("Get: %s\n", Argv->inputline);
    Argv->argc = argify(in->data + in->used, &Argv->argv);
    if (ptr != NULL)
      *ptr = '\n';
    dp = (struct Daemoncmd *) searchcmd(Argv->argv[0]);
    Argv->dc = dp;
    if (Argv->dc) {
#ifdef DEBUG
      printf("enter command %s\n", Argv->argv[0]);
#endif
      if (Argv->argc < dp->argc) {
	fprintf(Argv->out, "%d Usage: %s\r\n", dp->errorcode, dp->usage);
	fflush(Argv->out);
	verboselog("Put: %d Usage: %s\n", dp->errorcode, dp->usage);
      } else if (dp->argno != 0 && Argv->argc > dp->argno) {
	fprintf(Argv->out, "%d Usage: %s\r\n", dp->errorcode, dp->usage);
	fflush(Argv->out);
	verboselog("Put: %d Usage: %s\n", dp->errorcode, dp->usage);
      } else {
	Main = Argv->dc->main;
	if (Main) {
	  fflush(stdout);
	  (*Main) (client);
	}
      }
    } else {
      fprintf(Argv->out, "500 Syntax error or bad command\r\n");
      fflush(Argv->out);
      verboselog("Put: 500 Syntax error or bad command\r\n");
    }
    deargify(&Argv->argv);
  } else {
    if (Argv->dc) {
#ifdef DEBUG
      printf("enter data mode\n");
#endif
      Main = Argv->dc->main;
      if (Main) {
	fflush(stdout);
	(*Main) (client);
      }
    }
  }
  if (client->mode == 0) {
    if (dataleft > 0) {
      strncpy(in->data, lastend, dataleft);
#ifdef INNBBSDEBUG
      printf("***** try to copy %x %x %d bytes\n", in->data, lastend, dataleft);
#endif
    } else {
      dataleft = 0;
    }
    in->left += in->used - dataleft;
    in->used = dataleft;
  }
}

do_command()
{
}

void 
dopipesig(s)
  int             s;
{
  printf("catch sigpipe\n");
  signal(SIGPIPE, dopipesig);
}

int 
standaloneinit(port)
  char           *port;
{
  int             ndescriptors;
  FILE           *pf;
  char            pidfile[24];

  /* #ifndef NOFORK */
  if (!inetdstart)
  {
    if (fork())
      exit(0);
  }
  /* #endif */

  ndescriptors = getdtablesize();
  {
    int             s;
    for (s = 3; s < ndescriptors; s++)
      (void)close(s);
  }

  if (!inetdstart)
  {
    if (setsid() == -1) exit(0);

    signal(SIGHUP, SIG_IGN);

    if (fork()) exit(0);
  }

  umask(S_IWGRP | S_IWOTH);

  sprintf(pidfile, "/tmp/innbbsd-%s.pid", port);

  if ((pf = fopen(pidfile, "w")) != NULL)
  {
    fprintf(pf, "%d\n", getpid());
    fclose(pf);
  }
}

extern char    *optarg;
extern int      opterr, optind;

innbbsusage(name)
  char           *name;
{
  fprintf(stderr, "Usage: %s   [options] [port [path]]\n", name);
  fprintf(stderr, "   -v   (verbose log)\n");
  fprintf(stderr, "   -h|? (help)\n");
  fprintf(stderr, "   -n   (not to use in core dbz)\n");
  fprintf(stderr, "   -i   (start from inetd with wait option)\n");
  fprintf(stderr, "   -c   connections  (maximum number of connections accepted)\n");
  fprintf(stderr, "        default=%d\n", Maxclient);
  fprintf(stderr, "   -j   (keep history of junk article, default=none)\n");
}


#ifdef DEBUGNGSPLIT
main()
{
  char          **ngptr;
  char            buf[1024];
  gets(buf);
  ngptr = (char **)BNGsplit(buf);
  printf("line %s\n", buf);
  while (*ngptr != NULL) {
    printf("%s\n", *ngptr);
    ngptr++;
  }
}
#endif

static time_t   INNBBSDstartup;
innbbsdstartup()
{
  return INNBBSDstartup;
}

main(argc, argv)
  int             argc;
  char          **argv;
{

  char           *port, *path;
  int             c, errflag = 0;
  extern          INNBBSDhalt();
  /*
   * woju
   */
  setgid(BBSGID);
  setuid(BBSUID);
  chdir(BBSHOME);
  attach_SHM();
  resolve_boards();

  port = DefaultINNBBSPort;
  path = LOCALDAEMON;
  Junkhistory = 0;

  time(&INNBBSDstartup);
  openlog("innbbsd", LOG_PID | LOG_ODELAY, LOG_DAEMON);
  while ((c = getopt(argc, argv, "c:f:s:vhidn?j")) != -1)
    switch (c) {
    case 'j':
      Junkhistory = 1;
      break;
    case 'v':
      verboseon("innbbsd.log");
      break;
    case 'n':
      hisincore(0);
      break;
    case 'c':
      Maxclient = atoi(optarg);
      if (Maxclient < 0)
	Maxclient = 0;
      break;
    case 'i':{
	struct sockaddr_in6 there;
	int             len = sizeof(there);
	int             rel;
	if ((rel = getsockname(0, (struct sockaddr *) & there, &len)) < 0) {
	  fprintf(stdout, "You must run -i from inetd with inetd.conf line: \n");
	  fprintf(stdout, "service-port stream  tcp wait  bbs  /home/bbs/innbbsd innbbsd -i port\n");
	  fflush(stdout);
	  exit(5);
	}
	inetdstart = 1;
	startfrominetd(1);
      }
      break;
#ifdef DBZDEBUG
    case 'd':
      dbzdebug(1);
      break;
#endif
    case 's':
      Max_Art_Size = atol(optarg);
      if (Max_Art_Size < 0)
	Max_Art_Size = 0;
      break;
    case 'h':
    case '?':
    default:
      errflag++;
    }
  if (errflag > 0) {
    innbbsusage(argv[0]);
    return (1);
  }
  if (argc - optind >= 1) {
    port = argv[optind];
  }
  if (argc - optind >= 2) {
    path = argv[optind + 1];
  }
  standaloneinit(port);

  initial_bbs("feed");
  /*
   * Kaede if (!inetdstart) fprintf(stderr, "Try to listen in port %s and
   * path %s\n", port, path);
   */
  HISmaint();
  HISsetup();
  installinnbbsd();
  sethaltfunction(INNBBSDhalt);

  signal(SIGPIPE, dopipesig);
  inndchannel(port, path);
  HISclose();
}
